package com.ht.dsp.impl;

import com.ht.aems.fesmodel.PointManager;
import com.ht.aems.fesmodel.data.AnalogPoint;
import com.ht.aems.fesmodel.data.DiscretePoint;
import com.ht.aems.fesmodel.data.YCData;
import com.ht.aems.fesmodel.data.YXData;
import com.ht.aems.util.SocketManager;
import com.ht.dsp.DspSysModel;
import com.ht.dsp.SeConfReader;
import com.ht.dsp.SeExecutor;
import org.apache.log4j.Logger;

import java.io.*;
import java.net.Socket;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author Dong Shufeng
 *         Date: 2010-8-24
 */
public abstract class AbstractScadaUpdator {
    private static Logger log = Logger.getLogger(AbstractScadaUpdator.class);
    protected static final int POINT_TYPE_DISCRETE = 1;
    protected static final int POINT_TYPE_ANALOG = 2;

    //constants
    protected static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    protected static final String RTDB_SERVER = "RtdbServer.ip";
    protected static final int MAX_TRANS_NUM = 500000;
    protected static int failNumAllowed = 2;
    protected static byte[] toSend;

    // the data
    private Map<String, Integer> map_discrete = new HashMap<String, Integer>();
    private Map<String, Double> map_analog = new HashMap<String, Double>();
    private long timestamp = 0;
    private boolean isSendOver = false;
    private String hisDataDir = "/aems/datastore/";
    private Map<Integer, List<AnalogPoint>> analogsToRepeat = new HashMap<Integer, List<AnalogPoint>>();
    private Map<Integer, List<DiscretePoint>> discretesToRepeat = new HashMap<Integer, List<DiscretePoint>>();
    private String instance = "";

    protected AbstractScadaUpdator() {
        loadRepeatPoints();
        initial();
    }

    private static byte[] getBits(long a) { //
        byte[] b = new byte[4];
        for (int i = 3; i >= 0; i--) {
            b[i] = (byte) (a & 0x00FF);
            a = a >> 8;
        }
        return b;
    }

    private static void initial() {
        if (toSend != null)
            return;
        int len = 6;
        toSend = new byte[len];
        toSend[0] = 0x6B;
        toSend[len - 1] = 0x6C;
        int a = 1;
        for (int i = len - 2; i >= 1; i--) {
            toSend[i] = (byte) (a & 0x00FF);
            a = a >> 8;
        }
    }

    public void loadRepeatPoints() {
        try {
            analogsToRepeat.clear();
            discretesToRepeat.clear();
            PointManager.getInstance().reload();
            int count1 = 0;
            int count2 = 0;
            for (AnalogPoint p : PointManager.getInstance().getAnalogPoints().values()) {
                if (p.getTransmitFromId() != null && !p.getTransmitFromId().trim().equals("")
                        && !p.getTransmitFromId().trim().equalsIgnoreCase("null")) {
                    if (!analogsToRepeat.containsKey(p.getRtuId()))
                        analogsToRepeat.put(p.getRtuId(), new ArrayList<AnalogPoint>());
                    analogsToRepeat.get(p.getRtuId()).add(p);
                    count1++;
                }
            }
            for (DiscretePoint p : PointManager.getInstance().getDiscreatePoints().values()) {
                if (p.getTransmitFromId() != null && !p.getTransmitFromId().trim().equals("")
                        && !p.getTransmitFromId().trim().equalsIgnoreCase("null")) {
                    if (!discretesToRepeat.containsKey(p.getRtuId()))
                        discretesToRepeat.put(p.getRtuId(), new ArrayList<DiscretePoint>());
                    discretesToRepeat.get(p.getRtuId()).add(p);
                    count2++;
                }
            }
            log.info("There are " + count1 + " analog points should be repeated.");
            log.info("There are " + count2 + " discrete points should be repeated.");
        } catch (Exception ex) {
            ex.printStackTrace();
            analogsToRepeat.clear();
            discretesToRepeat.clear();
            log.warn("Failed load repeater measurement points from db: " + ex);
        }
    }

    public void save() throws RemoteException {
        if (hisDataDir != null) {
            try {
                StringBuffer content = new StringBuffer();
                long timestamp = DspSysModel.INSTANCE.getScadaDataTime();
                content.append(format.format(timestamp)).append("\n");
                for (String key : DspSysModel.INSTANCE.getScadaAnalog().keySet())
                    content.append(2).append("\t").append(key).append("\t").append(DspSysModel.INSTANCE.getScadaAnalog().get(key)).append("\n");
                for (String key : DspSysModel.INSTANCE.getScadaDiscrete().keySet())
                    content.append(1).append("\t").append(key).append("\t").append(DspSysModel.INSTANCE.getScadaDiscrete().get(key)).append("\n");
                FileWriter writer = new FileWriter(hisDataDir + "scada" + format.format(timestamp) + ".txt");
                writer.write(content.toString());
                writer.flush();
                writer.close();
                log.info("write file over");
            } catch (Exception e) {
                log.warn(e);
            }
        } else {
            log.warn("No dir is set to store scada data file.");
        }
    }

    public boolean order() {
        Socket s = SocketManager.getSocket(RTDB_SERVER);
        if (s == null) {
            return false;
        }
        //order
        try {
            setSendOver(false);
            s.getOutputStream().write(toSend);
            log.info("order command is sent!");
            int count = 0;
            while (!isSendOver && count < 20) {
                receiveData();
                count++;
            }
            return true;
        } catch (IOException e) {
            log.warn(e);
            SocketManager.closeSocket(RTDB_SERVER);
        } catch (InterruptedException e) {
            log.warn(e);
        }
        return false;
    }

    private byte[] execute(byte[] jobDesc) {
        if (jobDesc.length == 1 && jobDesc[0] == 0x6E)
            log.info("todo waken the standby application server!");
        else if (jobDesc.length == 1 && jobDesc[0] == 0x6D) {
            log.info("send over!");
            isSendOver = true;
            SocketManager.closeSocket(RTDB_SERVER);//close socket
            fillManualPoints();
            DspSysModel.INSTANCE.setScadaDataLocked(true);
            DspSysModel.INSTANCE.setScadaDataTime(timestamp);
            DspSysModel.INSTANCE.getScadaAnalog().clear();
            DspSysModel.INSTANCE.getScadaAnalog().putAll(map_analog);
            DspSysModel.INSTANCE.getScadaDiscrete().clear();
            DspSysModel.INSTANCE.getScadaDiscrete().putAll(map_discrete);
            DspSysModel.INSTANCE.setScadaDataLocked(false);
            fillFesPoints();
            SeConfReader.setReversedPoints(DspSysModel.INSTANCE.getScadaAnalog());
            SeExecutor.startAnalyseThread();
            map_discrete.clear();
            map_analog.clear();
        } else {
            BufferedReader reader = new BufferedReader(new StringReader(new String(jobDesc)));
            try {
                long l = format.parse(reader.readLine().trim()).getTime();
                if (l != timestamp) timestamp = l;

                String str;
                log.info(format.format(l));
                int i = 0;
                while ((str = reader.readLine()) != null) {
                    log.debug(str);
                    String[] temp = str.split("\\t", 3);
                    int type = Integer.parseInt(temp[0]);
                    String measureId = getMeasureId(temp[1]);
                    switch (type) {
                        case POINT_TYPE_DISCRETE: {
                            map_discrete.put(measureId, Integer.parseInt(temp[2]));
                            break;
                        }
                        case POINT_TYPE_ANALOG: {
                            map_analog.put(measureId, Double.parseDouble(temp[2]));
                            break;
                        }
                        default:
                            break;
                    }
                    i++;
                }
                log.info(i + " pieces of realtime data received at " + format.format(System.currentTimeMillis()));
            } catch (Exception e) {
                log.warn(e);
                log.warn("content is not support by the jobManager!");
            }
        }
        return null;
    }

    private void fillManualPoints() {
        log.info("Start filling manual points...");
        Properties p = new Properties();
        try {
            p.load(this.getClass().getResourceAsStream("/manualpoints.properties"));
            Set<String> keys = p.stringPropertyNames();
            for (String key : keys) {
                if (key.substring(0, 2).equals("1_"))
                    map_discrete.put(key.substring(2), Integer.parseInt(p.getProperty(key)));
                else
                    map_analog.put(key.substring(2), Double.parseDouble(p.getProperty(key)));
            }
            log.info("There are " + keys.size() + " pieces of manual points.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private byte[] readContent(Socket socket, int length) throws InterruptedException, IOException {
        byte[] content = new byte[length];
        int actualRead = 0;
        while (actualRead != length) {
            int n = socket.getInputStream().read(content, actualRead, length - actualRead);
            if (n == -1) Thread.sleep(10);
            else actualRead += n;
        }
        return content;
    }

    private void receiveData() throws IOException, InterruptedException {
        Socket socket = SocketManager.getSocket(RTDB_SERVER);
        if (socket == null) {
            return;
        }
        int start = socket.getInputStream().read();
        if (start == 0x6B) { //start code
            // 1 get data length
            byte[] num = readContent(socket, 4);
            int ch1 = num[0] & 0xFF;
            int ch2 = num[1] & 0xFF;
            int ch3 = num[2] & 0xFF;
            int ch4 = num[3] & 0xFF;
            if ((ch1 | ch2 | ch3 | ch4) < 0)
                throw new EOFException();
            //noinspection PointlessBitwiseExpression
            int length = ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
            if (length < 0 || length > MAX_TRANS_NUM) return;
            // 2 read it
            byte[] content = readContent(socket, length);
            // 3 deal with the content
            byte[] result = execute(content);
            // 4 affirm the information
            if (result != null) {
                socket.getOutputStream().write(0x6B);
                socket.getOutputStream().write(getBits(result.length));
                socket.getOutputStream().write(result);
            }
        }
    }

    protected void fillFesPoints() {
        for (int rtuId : analogsToRepeat.keySet()) {
            //repeat yc data from scada to fes point
            int n = 0;
            for (AnalogPoint p : analogsToRepeat.get(rtuId))
                if (map_analog.containsKey(p.getTransmitFromId()))
                    n++;
            if (n < 1)
                continue;
            YCData yc = new YCData();
            yc.setRemoteUnitId(rtuId);
            yc.setTimestamp(timestamp);
            int[] ycBits = new int[n];
            double[] ycData = new double[n];
            int index = 0;
            for (AnalogPoint p : analogsToRepeat.get(rtuId)) {
                if (!map_analog.containsKey(p.getTransmitFromId()))
                    continue;
                ycData[index] = map_analog.get(p.getTransmitFromId());
                ycBits[index++] = p.getPointNum();
            }
            yc.setYcBit(ycBits);
            yc.setData(ycData);
            DspSysModel.INSTANCE.getRtMeasureSaver().saveYC(yc);
        }

        //save yx data from scada to fes point
        for (int rtuId : discretesToRepeat.keySet()) {
            YXData yx = new YXData();
            yx.setRemoteUnitId(rtuId);
            yx.setTimestamp(timestamp);
            int n = 0;
            for (DiscretePoint p : discretesToRepeat.get(rtuId))
                if (map_discrete.containsKey(p.getTransmitFromId()))
                    n++;
            int[] yxBits = new int[n];
            int[] yxData = new int[n];
            int index = 0;
            for (DiscretePoint p : discretesToRepeat.get(rtuId)) {
                if (!map_discrete.containsKey(p.getTransmitFromId()))
                    continue;
                yxData[index] = map_discrete.get(p.getTransmitFromId());
                yxBits[index++] = p.getPointNum();
            }
            yx.setYxBit(yxBits);
            yx.setData(yxData);
            DspSysModel.INSTANCE.getRtMeasureSaver().saveYX(yx);
        }
    }

    public abstract String getMeasureId(String input);

    public abstract boolean update();

    public void setDatasourceIpPort(String ip, int port) throws RemoteException {
    }

    public String getInstance() {
        return instance;
    }

    public void setInstance(String instance) {
        this.instance = instance;
    }

    public boolean isSendOver() {
        return isSendOver;
    }

    public void setSendOver(boolean sendOver) {
        isSendOver = sendOver;
    }
}
